package com.kclmedu.thread;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/************
 * 利用BlockingQueue实现的固定大小的线程池
 * @author yejf
 * @date 2019-8-20
 */
public class FixedSizeThreadPool {

    /** 1. 用来存放所有待执行任务的任务列表仓库*/
    private BlockingQueue<Runnable> blockingQueue;

    /** 2. 用来负责执行任务的线程池*/
    private List<Thread> workers;

    /***********
     * 3. 构造线程池
     * @param poolSize 池的大小
     * @param taskSize 任务列表大小
     */
    public FixedSizeThreadPool(int poolSize, int taskSize) {
        //判断
        if(poolSize <= 0 || taskSize <= 0) {
            //抛出一个非法参数异常
            throw new IllegalArgumentException("非法参数");
        }
        //初始化待执行任务列表的大小
        this.blockingQueue = new LinkedBlockingQueue<>(taskSize);
        //初始化workers列表, 为保证线程安全，利用Collections进行包装
        this.workers = Collections.synchronizedList(new ArrayList<Thread>());
        //开始根据池的大小，循环执行
        for(int i = 0;i < poolSize; i++) {
            //每循环一次，创建一个工作Worker线程, 每个工作线程都以 当前的线程池为对象参数
            Worker worker = new Worker(this);
            //启动这个worker 线程
            worker.start();
            //并把这个worker线程添加到线程池中
            this.workers.add(worker);
        }
        //
        System.out.println("***** 线程池初始化完毕，开始接收用户添加任务...");
    }

    //4. 真正去干活的线程
    public static class Worker extends Thread {

        private FixedSizeThreadPool threadPool;

        /****
         * 构造方法
         * @param threadPool 线程池
         */
        public Worker(FixedSizeThreadPool threadPool) {
            this.threadPool = threadPool;
        }
        /**************
         * 要执行的方法
         */
        @Override
        public void run() {
            //循环去从blockingQueue中获取待执行任务【如果线程池状态是启动的或还有待执行任务时】
            //while(true) {
            while(this.threadPool.isWorking || this.threadPool.blockingQueue.size() > 0) {
                //打印出当前任务总数：
                System.out.printf("——*——*——* 线程池中任务总数：%d *——*——\n", this.threadPool.blockingQueue.size());
                Runnable task = null;
                //从待执行任务列表中获取要执行的任务
                try {
                    System.out.printf(">>> 工作线程[%s]从待执行任务列表中取出要执行的任务\n", getName());
                    //take方法特性：如果队列中没有值，线程会一直阻塞，直到队列中有值，并且该方法取得了该值。
                    // 如果线程池的状态是已关闭的，则不能以阻塞方式去取，而要切换成 非阻塞方式去取
                    if(this.threadPool.isWorking) {
                        //take() 方法是阻塞方法
                        task = this.threadPool.blockingQueue.take();
                    } else {
                        //而 poll() 方法是非阻塞方法
                        task = this.threadPool.blockingQueue.poll();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //取出任务后，直接执行这个任务即可
                if(null != task) {
                    System.out.printf("线程：%s 开始执行任务\n",getName());
                    //回调用户放进来的任务 [以 Runnable 做为可执行的任务]
                    task.run();
                    //输出一句话
                    System.out.printf("线程：%s 执行任务完毕.\n", Thread.currentThread().getName());
                }
            }
        }
    }

    /********
     * 5. 添加任务到此线程池中【阻塞方式】, 利用 offer 方法【非阻塞方法】添加任务进待执行任务列表中
     */
    public boolean submit(Runnable task) {
        if(task == null) {
            throw new NullPointerException("提交的任务对象是null");
        }
        //判断
        if(isWorking) {
            System.out.println("一个任务以【非阻塞方式】添加任务进待执行任务列表中...");
            return this.blockingQueue.offer(task);
        } else {
            //说明线程池要关闭，不允许再有新的任务加进来
            return false;
        }
    }
    /*****
     * 6. 添加任务到此线程池中【非阻塞方式】
     */
    public void putTask(Runnable task) {
        //判断，当线程池工作时，可以添加任务
        if(isWorking) {
            System.out.println("以阻塞方式添加任务进待执行任务列表中...");
            try {
                this.blockingQueue.put(task);
                System.out.println("---- 添加成功...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /** 定义一个变量，来记录线程池是否关闭的状态 */
    private volatile boolean isWorking = true;
    /*******************
     * 7. 关闭线程池, 这个过程是线程池最为重要的一环，主要涉及到如下几个方法
     *      1. 关闭的时候，仓库要停止有新的线程进来，也就是不允许再往池中添加任务了
     *      2. 关闭的时候，如果仓库中还有任务要执行，则执行完它
     *      3. 关闭的时候，如果再去仓库拿东西，就不能阻塞了，否则这个阻塞的线程就无法释放了
     *      4. 关闭的时候，如果还有线程被阻塞，我们要强行中断
     */
    public void shutdown() {
        System.out.println(" ********* 开始去关闭线程池....");
        //重置线程池的工作状态为 false
        isWorking = false;

        //迭代当前线程池中所有的worker线程，如果有处在阻塞状态的，通过发送中断信号去强行中断
        for(Thread worker : this.workers) {
            //判断
            if(worker.getState().equals(Thread.State.BLOCKED)
                    || worker.getState().equals(Thread.State.WAITING)) {
                //给worker线程发送中断信号
                worker.interrupt();
            }
        }
        System.out.println("******　线程池关闭完成...");
    }

}
